//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCertificates open source project
//
// Copyright (c) 2022 Apple Inc. and the SwiftCertificates project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import SwiftASN1
import _CryptoExtras
import Foundation

extension Certificate {
    /// An abstract representation of the cryptographic signature on a certificate.
    ///
    /// Certificates may have a wide range of signature types. This type provides a runtime
    /// abstraction across these types. It ensures that we understand the algorithm used to
    /// sign the certificate, and enables us to provide verification logic, without forcing
    /// users to wrestle with the wide variety of runtime types that may represent a
    /// signature.
    ///
    /// This type is almost entirely opaque. It can be validated by way of
    /// ``Certificate/PublicKey-swift.struct/isValidSignature(_:for:)-3cbor``, and it
    /// can be generated by ``Certificate/PrivateKey``s automatically when
    /// used by ``Certificate/init(version:serialNumber:publicKey:notValidBefore:notValidAfter:issuer:subject:signatureAlgorithm:extensions:issuerPrivateKey:)``.
    /// Otherwise, this type has essentially no behaviours.
    public struct Signature {
        @usableFromInline
        var backing: BackingSignature

        @inlinable
        internal init(backing: BackingSignature) {
            self.backing = backing
        }

        @inlinable
        internal init(signatureAlgorithm: SignatureAlgorithm, signatureBytes: ASN1BitString) throws {
            switch signatureAlgorithm {
            case .ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512:
                let signature = try ECDSASignature(derEncoded: signatureBytes.bytes)
                self.backing = .ecdsa(signature)
            case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption:
                let signature = _RSA.Signing.RSASignature(rawRepresentation: signatureBytes.bytes)
                self.backing = .rsa(signature)
            case .ed25519:
                guard signatureBytes.paddingBits == 0 else {
                    throw CertificateError.invalidSignatureForCertificate(
                        reason: "No padding bits are allowed on Ed25519 signatures"
                    )
                }
                let signature = Data(signatureBytes.bytes)
                self.backing = .ed25519(signature)
            default:
                throw CertificateError.unsupportedSignatureAlgorithm(reason: "\(signatureAlgorithm)")
            }
        }
    }
}

extension Certificate.Signature: Hashable {}

extension Certificate.Signature: Sendable {}

extension Certificate.Signature: CustomStringConvertible {
    public var description: String {
        switch backing {
        case .ecdsa:
            return "ECDSA"
        case .rsa:
            return "RSA"
        case .ed25519:
            return "Ed25519"
        }
    }
}

extension Certificate.Signature {
    @usableFromInline
    enum BackingSignature: Hashable, Sendable {
        case ecdsa(ECDSASignature)
        case rsa(_CryptoExtras._RSA.Signing.RSASignature)
        case ed25519(Data)

        @inlinable
        static func == (lhs: BackingSignature, rhs: BackingSignature) -> Bool {
            switch (lhs, rhs) {
            case (.ecdsa(let l), .ecdsa(let r)):
                return l == r
            case (.rsa(let l), .rsa(let r)):
                return l.rawRepresentation == r.rawRepresentation
            case (.ed25519(let l), .ed25519(let r)):
                return l == r
            default:
                return false
            }
        }

        @inlinable
        func hash(into hasher: inout Hasher) {
            switch self {
            case .ecdsa(let sig):
                hasher.combine(0)
                hasher.combine(sig)
            case .rsa(let digest):
                hasher.combine(1)
                hasher.combine(digest.rawRepresentation)
            case .ed25519(let sig):
                hasher.combine(2)
                hasher.combine(sig)
            }
        }
    }
}

extension ASN1BitString {
    @inlinable
    init(_ signature: Certificate.Signature) {
        switch signature.backing {
        case .ecdsa(let sig):
            var serializer = DER.Serializer()
            try! serializer.serialize(sig)
            self = ASN1BitString(bytes: serializer.serializedBytes[...])
        case .rsa(let sig):
            self = ASN1BitString(bytes: ArraySlice(sig.rawRepresentation))
        case .ed25519(let sig):
            self = ASN1BitString(bytes: ArraySlice(sig))
        }
    }
}

extension ASN1OctetString {
    @inlinable
    init(_ signature: Certificate.Signature) {
        switch signature.backing {
        case .ecdsa(let sig):
            var serializer = DER.Serializer()
            try! serializer.serialize(sig)
            self = ASN1OctetString(contentBytes: serializer.serializedBytes[...])
        case .rsa(let sig):
            self = ASN1OctetString(contentBytes: ArraySlice(sig.rawRepresentation))
        case .ed25519(let sig):
            self = ASN1OctetString(contentBytes: ArraySlice(sig))
        }
    }
}
